// 请求配置文件
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
// 引入进度条
import { close, start } from '@/utils/nporgress'
import router from '@/router'
import { ElMessage } from 'element-plus'

// 定义响应体参数
interface ResType<T> {
  code: number
  data?: T
  msg: string
  err?: string
}

const URL: string = '/api'

// 定义响应码
enum RequestEnums {
  TIMEOUT = 3000,
  OVERDUE = 600, // 登录失效
  FAIL = 999, // 请求失败
  SUCCESS = 200, // 请求成功
}

// 定义axios基础信息
const axiosConfig = {
  // 默认地址
  baseURL: URL as string,
  // 设置超时时间
  timeout: RequestEnums.TIMEOUT as number,
  // 跨域时候允许携带凭证
  withCredentials: true
}

class Request {
  // axios实例
  private instance: AxiosInstance

  public constructor(config: AxiosRequestConfig) {
    this.instance = axios.create(config)

    // 全局请求拦截
    this.instance.interceptors.request.use(
      (config) => {
        const token = window.sessionStorage.getItem('Authorization')
        if (token) {
          config.headers["Authorization"] = token
        }
        return config
      },
      (err: AxiosError) => {
        return Promise.reject(err)
      },
    )

    // 全局响应拦截
    this.instance.interceptors.response.use(
      (res: AxiosResponse) => {
        const { data, config } = res; // 解构

        if (data.code === RequestEnums.OVERDUE) {
          // 登录信息失效，应跳转到登录页面，并清空本地的token
          localStorage.setItem('Authorization', '');
          router.replace({
            path: '/login'
          })
          return Promise.reject(data);
        }
        // 全局错误信息拦截（防止下载文件的时候返回数据流，没有code，直接报错）
        if (data.code && data.code !== RequestEnums.SUCCESS) {
          ElMessage.error(data); // 此处也可以使用组件提示报错信息
          return Promise.reject(data)
        }
        return data;

        // return Promise.resolve(res)
      },
      (err: AxiosError) => {
        const { response } = err;
        if (response) {
          this.handleCode(response.status)
        }
        if (!window.navigator.onLine) {
          ElMessage.error('网络连接失败');
          // 可以跳转到错误页面，也可以不做操作
          // return router.replace({
          //   path: '/404'
          // });
        }
        // return Promise.reject(err)
      },
    )
  }

  handleCode(code: number): void {
    switch (code) {
      case 401:
        ElMessage.error('登录失败，请重新登录');
        break;
      case 404:
        ElMessage.error('未找到路径，请联系管理员');
        break;
      default:
        ElMessage.error('请求失败请联系管理员');
        break;
    }
  }

  // 常用方法封装
  get<T>(url: string, params?: object): Promise<ResType<T>> {
    return new Promise((resolve, reject) => {
      start()
      this.instance
        .get(url, { params })
        .then(res => {
          close()
          resolve(res.data)
        })
        .catch(err => {
          close()
          reject(err.data)
        })
    })
  }
  post<T>(url: string, params?: object): Promise<ResType<T>> {
    return new Promise((resolve, reject) => {
      start()
      this.instance
        .post(url, params)
        .then(res => {
          close()
          resolve(res.data)
        })
        .catch(err => {
          close()
          reject(err.data)
        })
    })
  }
  put<T>(url: string, params?: object): Promise<ResType<T>> {
    return new Promise((resolve, reject) => {
      start()
      this.instance
        .put(url, params)
        .then(res => {
          close()
          resolve(res.data)
        })
        .catch(err => {
          close()
          reject(err.data)
        })
    })
  }
  delete<T>(url: string, params?: object): Promise<ResType<T>> {
    return new Promise((resolve, reject) => {
      start()
      this.instance
        .delete(url, params)
        .then(res => {
          close()
          resolve(res.data)
        })
        .catch(err => {
          close()
          reject(err.data)
        })
    })
  }
  upload<T>(url: string, file: unknown): Promise<ResType<T>> {
    return new Promise((resolve, reject) => {
      start()
      this.instance
        .post(url, file, {
          headers: { 'Content-Type': 'multipart/form-data' },
        })
        .then(res => {
          close()
          resolve(res.data)
        })
        .catch(err => {
          close()
          reject(err.data)
        })
    })
  }

  download(url: string) {
    const iframe = document.createElement('iframe')
    iframe.style.display = 'none'
    iframe.src = url
    iframe.onload = function remove() {
      document.body.removeChild(iframe)
    }
    document.body.appendChild(iframe)
  }
}

export default new Request(axiosConfig)
